/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.flexengine.sound;

import com.flexengine.component.ProgressBar;
import com.flexengine.engine.FlexEngine;
import com.flexengine.sound.filter.FilteredSoundStream;
import com.flexengine.sound.filter.SoundFilter;
import com.flexengine.logger.Logger;
import com.flexengine.utils.ResourceManager;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

/**
 * @author Luann R. Athayde
 * @version 1.0
 * @since 1.0
 */
public class SoundManager {

    public static final int          DEFAULT_MAX_SIMULTANEOUS_SOUNDS = 32;
    
    private final ArrayList<SoundPlayer>   players;
    private int                            maxSimultaneousSounds;
    private boolean                        enable;
    private final HashMap<String,Sound>    soundsMap;
    
    public SoundManager() {
        maxSimultaneousSounds = DEFAULT_MAX_SIMULTANEOUS_SOUNDS;
        players = new ArrayList<>();
        soundsMap = new HashMap<>();
        enable = true;
    }
    
    private void recursiveLoad(File files[], ProgressBar pBar) {
        for (File tmp : files) {
            if( tmp.isDirectory() ) {
                File fTmp[] = tmp.listFiles();
                recursiveLoad(fTmp, pBar);
            } else if( tmp.isFile() ) {
                if( isValidSound(tmp) ) {
                    Logger.logMessage("SoundMap - loadSounds(): Sound founded...");
                    String name = tmp.getName();
                    String key  = name.substring(0,name.indexOf('.'));
                    String file = tmp.getPath();
                    file = file.substring(11, file.length());
                    Logger.logMessage("SoundMap - loadSounds(): name = "+key);
                    Logger.logMessage("SoundMap - loadSounds(): file = "+file);
                    Sound sound = SoundManager.getSound(file);
                    Logger.logMessage("SoundMap - loadSounds(): sound = "+sound);
                    soundsMap.put(key, sound);
                    if( pBar!=null ) {
                        pBar.incrementProgress();
                    }
                }
            }
        }
    }
    
    /**
     * Load all sounds in recursive mode conatis in ./resources/folder and subfolders...
     * @param folder
     * @param pBar
     */
    public void loadSounds(final String folder, ProgressBar pBar) {
        Logger.logMessage("SoundMap - loadSounds(): Loading sounds...");
        try {
            File fRes = new File(ResourceManager.getLocale("/"+folder));
            Logger.logMessage("SoundMap - loadSounds(): path = "+fRes.getPath());
            File fSounds[] = fRes.listFiles();
            recursiveLoad(fSounds, pBar);
        } catch(Exception e) {
            Logger.logError("SoundManager::loadSounds("+folder+"): "+e);
            FlexEngine.getInstance().stop();
        }
    }
    /**
     * Load all sounds in recursive mode conatis in ./resources/folder and subfolders...
     * @param folder
     */
    public void loadSounds(final String folder) {
        loadSounds("", null);
    }
    /**
     * Load all sounds in recursive mode contains in ./resources/ and subfolders...
     */
    public void loadSounds() {
        loadSounds("", null);
    }

    
    public long soundsToLoad() {
        return soundsToLoad("");
    }
    private long recursiveCount(File[] files) {
        long count = 0;
        for (File file : files) {
            if (file.isFile()) {
                if ( isValidSound(file) ) {
                    count++;
                }
            } else {
                count += recursiveCount(file.listFiles());
            }
        }
        return count;
    }
    public long soundsToLoad(String folder) {
        try {
            File fRes = new File(ResourceManager.getLocale(folder));
            File fTexs[] = fRes.listFiles();
            return recursiveCount(fTexs);
        } catch (Exception e) {
            Logger.logError("SoundManager - soundsToLoad(): "+e);
        }
        return 0;
    }
    
    public int size() {
        return soundsMap.size();
    }

    public boolean isEmpty() {
        return soundsMap.isEmpty();
    }

    public boolean containsKey(String soundKey) {
        return soundsMap.containsKey(soundKey);
    }

    public boolean containsValue(Sound value) {
        return soundsMap.containsValue(value);
    }

    public Sound get(String soundKey) {
        return soundsMap.get(soundKey);
    }
    public Sound get(String soundKey, String path) {
        Sound ret = soundsMap.get(soundKey);
        if( ret == null ) {
            ret = SoundManager.getSound(path);
            this.put(soundKey, ret);
        }
        return ret;
    }

    public Sound put(String soundKey, Sound sound) {
        return soundsMap.put(soundKey, sound);
    }

    public Sound remove(String soundKey) {
        return soundsMap.remove(soundKey);
    }

    public void clear() {
        soundsMap.clear();
    }

    public Collection<Sound> values() {
        return soundsMap.values();
    }

    public Set<String> getKeySet()  {
        return soundsMap.keySet();
    }
    
    public synchronized SoundPlayer play(Sound sound, SoundFilter filter, boolean loop) {
        if( sound==null ) {
            Logger.logError(getClass().getSimpleName()+" - play(): Sound can not be NULL...");
            return null;
        }
        if( !enable ) {
            Logger.logWarning(getClass().getSimpleName()+" - play(): The sound suport is disabled!");
            return null;
        }
        InputStream is;
        SoundPlayer player = new SoundPlayer(sound);
        try {
            if( players.size()>=maxSimultaneousSounds ) {
                throw new MaxSimultaneousSoundException("Max simultaneos channels is alread used! Please stop sounds to do that...");
            }
            if( sound.getSoundType() != Sound.SOUND_TYPE_MP3 && 
                    sound.getSoundType() != Sound.SOUND_TYPE_UNK && 
                    sound.getSamples() != null ) {
                if( filter != null ) {
                    if( loop ) {
                        is = new FilteredSoundStream(
                                new LoopByteArrayInputStream(sound.getSamples()),
                                filter);
                    } else {
                        is = new FilteredSoundStream(
                                new ByteArrayInputStream(sound.getSamples()), 
                                filter);
                    }
                } else {
                    if( loop ) {
                        is = new LoopByteArrayInputStream(sound.getSamples());
                    } else {
                        is = new ByteArrayInputStream(sound.getSamples());
                    }
                }
            } else {
                if( loop ) {
                    is = new LoopByteArrayInputStream(sound.getSoundFile());
                } else {
                    is = new FileInputStream(sound.getSoundFile());
                }
            }
            players.add(player);
            Thread tPlayer = new Thread(new SoundPlayerTask(player, is), "playSound");
            tPlayer.start();
        } catch(MaxSimultaneousSoundException | FileNotFoundException e) {
            Logger.logError(getClass().getSimpleName()+" play("+sound+","+filter+","+loop+"): \n" + "\tException: "+e);
            player = null;
        }
        return player;
    }
    public synchronized SoundPlayer play(Sound sound, SoundFilter filter) {
        return play(sound, filter, false);
    }
    public synchronized SoundPlayer play(Sound sound, boolean loop) {
        return play(sound, null, loop);
    }
    public synchronized SoundPlayer play(Sound sound) {
        return play(sound, null, false);
    }
    
    public synchronized SoundPlayer playAndWait(Sound sound) {
        if( sound==null ) {
            Logger.logError(getClass().getSimpleName()+" - play(): Sound can not be NULL...");
            return null;
        }
        if( !enable ) {
            Logger.logWarning(getClass().getSimpleName()+" - play(): The sound suport is disable!");
            return null;
        }
        InputStream is;
        SoundPlayer player = new SoundPlayer(sound);
        try {
            if( players.size()>=maxSimultaneousSounds ) {
                throw new MaxSimultaneousSoundException("Max simultaneos channels is alread used! Please stop sounds to do that...");
            }
            if( sound.getSoundType() != Sound.SOUND_TYPE_MP3 && 
                sound.getSoundType()!= Sound.SOUND_TYPE_UNK && 
                sound.getSamples() != null ) {
                is = new ByteArrayInputStream(sound.getSamples());
            } else {
                is = new FileInputStream(sound.getSoundFile());
            }
            players.add(player);
            
            // -- play with out new trhead...
            player.play(is);
            
            // -- remove player...
            players.remove(player);
        } catch(MaxSimultaneousSoundException | FileNotFoundException e) {
            Logger.logError(getClass().getSimpleName()+" playAndWait("+sound+"): \n" + "\tException: "+e);
            player = null;
        }
        return player;
    }
    
    public void pauseAll() {
        Logger.logMessage(getClass().getSimpleName()+" - Pausing all sounds...");
        for(int i=0;i<players.size();i++) {
            SoundPlayer sp = players.get(i);
            sp.pause();
        }
    }
    
    public void stopAll() {
        Logger.logMessage(getClass().getSimpleName()+" - Stoping all sounds...");
        for(int i=0;i<players.size();i++) {
            SoundPlayer sp = players.get(i);
            sp.stop();
        }
    }
    
    public void continueAll() {
        Logger.logMessage(getClass().getSimpleName()+" - Continuing all sounds...");
        for(int i=0;i<players.size();i++) {
            SoundPlayer sp = players.get(i);
            sp.play();
        }
    }
    
    /**
     * 
     * @param channel
     * @return 
     */
    public SoundPlayer newSoundPlayer(Sound sound, int channel) {
        if( players.get(channel)!=null ) {
            SoundPlayer sp = players.get(channel);
            sp.stop();
        }
        SoundPlayer player = new SoundPlayer(sound);
        players.set(channel, player);
        return player;
    }
    
    public SoundPlayer newSoundPlayer(Sound sound) {
        return newSoundPlayer(sound, 0);
    }
    
    public SoundPlayer getSoundPlayer(int channel) {
        return players.get(channel);
    }

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
        if( !enable ) {
            stopAll();
        }
    }

    public void setMaxSimultaneousSounds(int maxSimultaneousSounds) {
        this.maxSimultaneousSounds = maxSimultaneousSounds;
        if( this.maxSimultaneousSounds > DEFAULT_MAX_SIMULTANEOUS_SOUNDS ) {
            this.maxSimultaneousSounds = DEFAULT_MAX_SIMULTANEOUS_SOUNDS;
        } else if( this.maxSimultaneousSounds<0 ) {
            this.maxSimultaneousSounds = 2;
        }
    }

    public int getMaxSimultaneousSounds() {
        return maxSimultaneousSounds;
    }
    
    private class SoundPlayerTask implements Runnable {

        private final SoundPlayer player;
        private final InputStream is;
        
        public SoundPlayerTask(SoundPlayer player, InputStream is) {
            this.player = player;
            this.is     = is;
        }
        
        @Override
        public void run() {
            player.play(is);
            players.remove(player);
        }
    }
    
    public static boolean isValidSound(File file) {
        return file.getName().endsWith(".ogg") || file.getName().endsWith(".wav")
                || file.getName().endsWith(".wave") || file.getName().endsWith(".mp3")
                || file.getName().endsWith(".mid") || file.getName().endsWith(".midi");
    }
    
    public static Sound getSound(String soundFile) {
        return new Sound(ResourceManager.getLocale(soundFile));
    }
    
    private static SoundManager instance;
    public static SoundManager getInstance() {
        if( instance == null ) {
            instance = new SoundManager();
        }
        return instance;
    }
    
}
